#ifndef __CONNECTION_H__
#define __CONNECTION_H__

#include <boost/asio.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/tuple/tuple.hpp>
#include <iomanip>
#include <string>
#include <sstream>
#include <vector>
#include "../Common/Packet.hpp"

class CConnection
{
public:
	CConnection( boost::asio::io_service& io_service ) : m_Socket( io_service )
	{
	}

	virtual ~CConnection( void )
	{
	}

public:
	// Get the underlying socket. Used for making a connection or for accepting
	// an incoming connection.
	boost::asio::ip::tcp::socket& GetSocket( void )
	{
		return m_Socket;
	}

	// Asynchronously write a data structure to the socket.
	template< typename T, typename Handler > void async_write( const T& t, Handler handler )
	{
		// Serialize the data first so we know how large it is.
		std::ostringstream ArchiveStream;
		
		boost::archive::text_oarchive Archive( ArchiveStream );
		Archive << t;

		m_OutboundData = ArchiveStream.str();

		// Format the header.
		std::ostringstream HeaderStream;
		HeaderStream << std::setw( eHeaderLength ) << std::hex << m_OutboundData.size();

		if( !HeaderStream || HeaderStream.str().size() != eHeaderLength )
		{
			// Something went wrong, inform the caller.
			boost::system::error_code error( boost::asio::error::invalid_argument );
			m_Socket.get_io_service().post( boost::bind(handler, error) );
			return;
		}

		m_OutboundHeader = HeaderStream.str();

		// Write the serialized data to the socket. We use "gather-write" to send
		// both the header and the data in a single write operation.
		std::vector< boost::asio::const_buffer > vecBuffer;
		vecBuffer.push_back( boost::asio::buffer(m_OutboundHeader) );
		vecBuffer.push_back( boost::asio::buffer(m_OutboundData) );
		boost::asio::async_write( m_Socket, vecBuffer, handler );

//		boost::asio::async_write( m_Socket, boost::asio::buffer(m_OutboundHeader), handler );
//		boost::asio::async_write( m_Socket, boost::asio::buffer(m_OutboundData), handler );
	}

	/// Asynchronously read a data structure from the socket.
	template< typename T, typename Handler > void async_read( T& t, Handler handler )
	{
		// Issue a read operation to read exactly the number of bytes in a header.
		
		boost::asio::async_read( m_Socket,
								 boost::asio::buffer( m_InboundHeader ),
								 boost::bind(&CConnection::handle_read_header< T, Handler >, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred, boost::ref(t), boost::make_tuple(handler)) );
	}

	// Handle a completed read of a message header. The handler is passed using
	// a tuple since boost::bind seems to have trouble binding a function object
	// created using boost::bind as a parameter.
	template< typename T, typename Handler > void handle_read_header( const boost::system::error_code& e, size_t bytes_transferred, T& t, boost::tuple< Handler > handler )
	{
		if( e )
		{
			boost::get< 0 >( handler )( e, bytes_transferred );
			return;
		}

		// Determine the length of the serialized data.
		std::istringstream is( std::string(m_InboundHeader, eHeaderLength) );
		std::size_t InboundDataSize = 0;
		if( !(is >> std::hex >> InboundDataSize) )
		{
			// Header doesn't seem to be valid. Inform the caller.
			boost::system::error_code error( boost::asio::error::invalid_argument );
			boost::get< 0 >( handler )( error, bytes_transferred );
			return;
		}

		// Start an asynchronous call to receive the data.
		m_InboundData.resize( InboundDataSize );
			
		boost::asio::async_read( m_Socket,
								 boost::asio::buffer( m_InboundData ),
								 boost::bind(&CConnection::handle_read_data< T, Handler >, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred, boost::ref(t), handler) );
	}
	
	// Handle a completed read of message data.
	template< typename T, typename Handler > void handle_read_data( const boost::system::error_code& e, size_t bytes_transferred, T& t, boost::tuple<Handler> handler )
	{
		if( e )
		{
			boost::get< 0 >( handler )( e, bytes_transferred );
			return;
		}
	
		// Extract the data structure from the data just received.
		try
		{
			std::string strArchiveData( &m_InboundData[0], m_InboundData.size() );

			std::istringstream ArchiveStream( strArchiveData );

			boost::archive::text_iarchive Archive( ArchiveStream );

//			ProcessingPacketData( Archive, t );

			SCommonPacket CommonPacket;
			Archive >> CommonPacket;

			SCommonPacket* pCommonPacket = NULL;

			switch( CommonPacket.nHeader )
			{
			case CLIENT_TO_SERVER_LOGIN:
				{
					std::istringstream ArchiveStream( strArchiveData );
					boost::archive::text_iarchive Archive( ArchiveStream );
					
					client_to_server_login msg_client_to_server_login;
					Archive >> msg_client_to_server_login;

					pCommonPacket = new client_to_server_login;
					memcpy( pCommonPacket, &msg_client_to_server_login, sizeof(client_to_server_login) );
				}
				break;

			case SERVER_TO_CLIENT_LOGIN_SUCCESSED:
				{
					std::istringstream ArchiveStream( strArchiveData );
					boost::archive::text_iarchive Archive( ArchiveStream );

					server_to_client_login_successed msg_server_to_client_login_successed;
					Archive >> msg_server_to_client_login_successed;

					pCommonPacket = new server_to_client_login_successed;
					memcpy( pCommonPacket, &msg_server_to_client_login_successed, sizeof(server_to_client_login_successed) );
				}
				break;

			case SERVER_TO_CLIENT_LOGIN_FAILED:
				{
					std::istringstream ArchiveStream( strArchiveData );
					boost::archive::text_iarchive Archive( ArchiveStream );

					server_to_client_login_failed msg_server_to_client_login_failed;
					Archive >> msg_server_to_client_login_failed;

					pCommonPacket = new server_to_client_login_failed;
					memcpy( pCommonPacket, &msg_server_to_client_login_failed, sizeof(server_to_client_login_failed) );
				}
				break;
			}

			t.push_back( pCommonPacket );
			
		}
		catch( std::exception& e )
		{
			// Unable to decode data.
			boost::system::error_code error( boost::asio::error::invalid_argument );
			boost::get< 0 >( handler )( error, bytes_transferred );
			return;
		}

		// Inform caller that data has been received ok.
		boost::get< 0 >( handler )( e, bytes_transferred );
	}

//	template< typename T > void ProcessingPacket( boost::archive::text_iarchive& Archive, T& t )	{}

private:
	enum { eHeaderLength = 8 };										// The size of a fixed length header.
	
	boost::asio::ip::tcp::socket	m_Socket;						// The underlying socket.

	std::string						m_OutboundHeader;				// Holds an outbound header.
	std::string						m_OutboundData;					// Holds the outbound data.

	char							m_InboundHeader[eHeaderLength];	// Holds an inbound header.
	std::vector< char >				m_InboundData;					// Holds the inbound data.
};

typedef boost::shared_ptr< CConnection > ConnectionPtr;

#endif // __CONNECTION_H__